<!DOCTYPE html>
<html lang=en>
<head>
    <!-- so meta -->
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="HandheldFriendly" content="True">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
    <meta name="description" content="Java NIO？看这一篇就够了！现在使用NIO的场景越来越多，很多网上的技术框架或多或少的使用NIO技术，譬如Tomcat，Jetty。学习和掌握NIO技术已经不是一个JAVA攻城狮的加分技能，而是一个必备技能。在前面2篇文章《什么是Zero-Copy?》和《NIO相关基础篇》中我们学习了NIO的相关理论知识，而在本篇中我们一起来学习一下Java NIO的实战知识。全文较长，建议先马后看（记得关">
<meta property="og:type" content="article">
<meta property="og:title" content="JavaNIO">
<meta property="og:url" content="http://yoursite.com/2019/05/28/JavaNIO/index.html">
<meta property="og:site_name" content="TmoonSite">
<meta property="og:description" content="Java NIO？看这一篇就够了！现在使用NIO的场景越来越多，很多网上的技术框架或多或少的使用NIO技术，譬如Tomcat，Jetty。学习和掌握NIO技术已经不是一个JAVA攻城狮的加分技能，而是一个必备技能。在前面2篇文章《什么是Zero-Copy?》和《NIO相关基础篇》中我们学习了NIO的相关理论知识，而在本篇中我们一起来学习一下Java NIO的实战知识。全文较长，建议先马后看（记得关">
<meta property="og:locale" content="en_US">
<meta property="article:published_time" content="2019-05-27T17:23:08.000Z">
<meta property="article:modified_time" content="2020-01-07T10:48:14.729Z">
<meta property="article:author" content="Tmoonlight">
<meta name="twitter:card" content="summary">
    
    
        
          
              <link rel="shortcut icon" href="/images/favicon.ico">
          
        
        
          
            <link rel="icon" type="image/png" href="/images/logo2.gif" sizes="192x192">
          
        
        
          
            <link rel="apple-touch-icon" sizes="180x180" href="/images/logo2.gif">
          
        
    
    <!-- title -->
    <title>JavaNIO</title>
    <!-- styles -->
    
<link rel="stylesheet" href="/css/style.css">

    <!-- persian styles -->
    
      
<link rel="stylesheet" href="/css/rtl.css">

    
    <!-- rss -->
    
    
<meta name="generator" content="Hexo 4.2.0"></head>

<body class="max-width mx-auto px3 ltr">
    
      <div id="header-post">
  <a id="menu-icon" href="#"><i class="fas fa-bars fa-lg"></i></a>
  <a id="menu-icon-tablet" href="#"><i class="fas fa-bars fa-lg"></i></a>
  <a id="top-icon-tablet" href="#" onclick="$('html, body').animate({ scrollTop: 0 }, 'fast');" style="display:none;"><i class="fas fa-chevron-up fa-lg"></i></a>
  <span id="menu">
    <span id="nav">
      <ul>
         
          <li><a href="/">Home</a></li>
         
          <li><a href="/about/">About</a></li>
         
          <li><a href="/archives/">Writing</a></li>
         
          <li><a href="/projects_url">Projects</a></li>
        
      </ul>
    </span>
    <br/>
    <span id="actions">
      <ul>
        
        <li><a class="icon" href="/2019/05/29/linux%E9%80%92%E5%BD%92%E5%88%9B%E5%BB%BA%E7%9B%AE%E5%BD%95/"><i class="fas fa-chevron-left" aria-hidden="true" onmouseover="$('#i-prev').toggle();" onmouseout="$('#i-prev').toggle();"></i></a></li>
        
        
        <li><a class="icon" href="/2019/05/05/java%E7%9A%84lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F/"><i class="fas fa-chevron-right" aria-hidden="true" onmouseover="$('#i-next').toggle();" onmouseout="$('#i-next').toggle();"></i></a></li>
        
        <li><a class="icon" href="#" onclick="$('html, body').animate({ scrollTop: 0 }, 'fast');"><i class="fas fa-chevron-up" aria-hidden="true" onmouseover="$('#i-top').toggle();" onmouseout="$('#i-top').toggle();"></i></a></li>
        <li><a class="icon" href="#"><i class="fas fa-share-alt" aria-hidden="true" onmouseover="$('#i-share').toggle();" onmouseout="$('#i-share').toggle();" onclick="$('#share').toggle();return false;"></i></a></li>
      </ul>
      <span id="i-prev" class="info" style="display:none;">Previous post</span>
      <span id="i-next" class="info" style="display:none;">Next post</span>
      <span id="i-top" class="info" style="display:none;">Back to top</span>
      <span id="i-share" class="info" style="display:none;">Share post</span>
    </span>
    <br/>
    <div id="share" style="display: none">
      <ul>
  <li><a class="icon" href="http://www.facebook.com/sharer.php?u=http://yoursite.com/2019/05/28/JavaNIO/" target="_blank" rel="noopener"><i class="fab fa-facebook " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="https://twitter.com/share?url=http://yoursite.com/2019/05/28/JavaNIO/&text=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-twitter " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://www.linkedin.com/shareArticle?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-linkedin " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="https://pinterest.com/pin/create/bookmarklet/?url=http://yoursite.com/2019/05/28/JavaNIO/&is_video=false&description=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-pinterest " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="mailto:?subject=JavaNIO&body=Check out this article: http://yoursite.com/2019/05/28/JavaNIO/"><i class="fas fa-envelope " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="https://getpocket.com/save?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-get-pocket " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://reddit.com/submit?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-reddit " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://www.stumbleupon.com/submit?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-stumbleupon " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://digg.com/submit?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-digg " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://www.tumblr.com/share/link?url=http://yoursite.com/2019/05/28/JavaNIO/&name=JavaNIO&description=" target="_blank" rel="noopener"><i class="fab fa-tumblr " aria-hidden="true"></i></a></li>
  <li><a class="icon" href="https://news.ycombinator.com/submitlink?u=http://yoursite.com/2019/05/28/JavaNIO/&t=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-hacker-news " aria-hidden="true"></i></a></li>
</ul>

    </div>
    <div id="toc">
      <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-NIO？看这一篇就够了！"><span class="toc-number">1.</span> <span class="toc-text">Java NIO？看这一篇就够了！</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#一、概述"><span class="toc-number">2.</span> <span class="toc-text">一、概述</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Channel"><span class="toc-number">2.1.</span> <span class="toc-text">Channel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer"><span class="toc-number">2.2.</span> <span class="toc-text">Buffer</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector"><span class="toc-number">2.3.</span> <span class="toc-text">Selector</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#二、FileChannel"><span class="toc-number">3.</span> <span class="toc-text">二、FileChannel</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#传统IO-vs-NIO"><span class="toc-number">3.1.</span> <span class="toc-text">传统IO vs NIO</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer的使用"><span class="toc-number">3.2.</span> <span class="toc-text">Buffer的使用</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#三、SocketChannel"><span class="toc-number">4.</span> <span class="toc-text">三、SocketChannel</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#四、TCP服务端的NIO写法"><span class="toc-number">5.</span> <span class="toc-text">四、TCP服务端的NIO写法</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#ServerSocketChannel"><span class="toc-number">5.1.</span> <span class="toc-text">ServerSocketChannel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector-1"><span class="toc-number">5.2.</span> <span class="toc-text">Selector</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#SelectionKey"><span class="toc-number">5.3.</span> <span class="toc-text">SelectionKey</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#通过Selector选择通道"><span class="toc-number">5.4.</span> <span class="toc-text">通过Selector选择通道</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#五、内存映射文件"><span class="toc-number">6.</span> <span class="toc-text">五、内存映射文件</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#概念"><span class="toc-number">6.1.</span> <span class="toc-text">概念</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#案例对比"><span class="toc-number">6.2.</span> <span class="toc-text">案例对比</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#六、其余功能介绍"><span class="toc-number">7.</span> <span class="toc-text">六、其余功能介绍</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Scatter-Gatter"><span class="toc-number">7.1.</span> <span class="toc-text">Scatter&#x2F;Gatter</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#transferFrom-amp-transferTo"><span class="toc-number">7.2.</span> <span class="toc-text">transferFrom &amp; transferTo</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Pipe"><span class="toc-number">7.3.</span> <span class="toc-text">Pipe</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#DatagramChannel"><span class="toc-number">7.4.</span> <span class="toc-text">DatagramChannel</span></a></li></ol></li></ol>
    </div>
  </span>
</div>

    
    <div class="content index py4">
        
        <article class="post" itemscope itemtype="http://schema.org/BlogPosting">
  <header>
    
    <h1 class="posttitle" itemprop="name headline">
        JavaNIO
    </h1>



    <div class="meta">
      <span class="author" itemprop="author" itemscope itemtype="http://schema.org/Person">
        <span itemprop="name">TmoonSite</span>
      </span>
      
    <div class="postdate">
      
        <time datetime="2019-05-27T17:23:08.000Z" itemprop="datePublished">2019-05-28</time>
        
      
    </div>


      

      

    </div>
  </header>
  

  <div class="content" itemprop="articleBody">
    <h2 id="Java-NIO？看这一篇就够了！"><a href="#Java-NIO？看这一篇就够了！" class="headerlink" title="Java NIO？看这一篇就够了！"></a>Java NIO？看这一篇就够了！</h2><p>现在使用NIO的场景越来越多，很多网上的技术框架或多或少的使用NIO技术，譬如Tomcat，Jetty。学习和掌握NIO技术已经不是一个JAVA攻城狮的加分技能，而是一个必备技能。在前面2篇文章《<a href="http://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483913&idx=1&sn=2da53737b8e8908cf3efdae9621c9698&chksm=fb0be89dcc7c618b0d5a1ba8ac654295454cfc2fa81fbae5a6de49bf0a91a305ca707e9864fc&scene=21#wechat_redirect" target="_blank" rel="noopener">什么是Zero-Copy?</a>》和《<a href="http://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483907&idx=1&sn=3d5e1384a36bd59f5fd14135067af1c2&chksm=fb0be897cc7c61815a6a1c3181f3ba3507b199fd7a8c9025e9d8f67b5e9783bc0f0fe1c73903&scene=21#wechat_redirect" target="_blank" rel="noopener">NIO相关基础篇</a>》中我们学习了NIO的相关理论知识，而在本篇中我们一起来学习一下Java NIO的实战知识。全文较长，建议先马后看（记得关注不迷路）。</p>
<h2 id="一、概述"><a href="#一、概述" class="headerlink" title="一、概述"></a>一、概述</h2><p>NIO主要有三大核心部分：Channel(通道)，Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作，而NIO基于Channel和Buffer(缓冲区)进行操作，数据总是从通道读取到缓冲区中，或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件（比如：连接打开，数据到达）。因此，单个线程可以监听多个数据通道。</p>
<p>NIO和传统IO（一下简称IO）之间第一个最大的区别是，IO是面向流的，NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节，直至读取所有字节，它们没有被缓存在任何地方。此外，它不能前后移动流中的数据。如果需要前后移动从流中读取的数据，需要先将它缓存到一个缓冲区。NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是，还需要检查是否该缓冲区中包含所有您需要处理的数据。而且，需确保当更多的数据读入缓冲区时，不要覆盖缓冲区里尚未处理的数据。</p>
<p>IO的各种流是阻塞的。这意味着，当一个线程调用read() 或 write()时，该线程被阻塞，直到有一些数据被读取，或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式，使一个线程从某通道发送请求读取数据，但是它仅能得到目前可用的数据，如果目前没有数据可用时，就什么都不会获取。而不是保持线程阻塞，所以直至数据变得可以读取之前，该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道，但不需要等待它完全写入，这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作，所以一个单独的线程现在可以管理多个输入和输出通道（channel）。</p>
<h3 id="Channel"><a href="#Channel" class="headerlink" title="Channel"></a>Channel</h3><p>首先说一下Channel，国内大多翻译成“通道”。Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的，譬如：InputStream, OutputStream.而Channel是双向的，既可以用来进行读操作，又可以用来进行写操作。</p>
<p>NIO中的Channel的主要实现有：</p>
<ul>
<li><p>FileChannel</p>
</li>
<li><p>DatagramChannel</p>
</li>
<li><p>SocketChannel</p>
</li>
<li><p>ServerSocketChannel</p>
</li>
</ul>
<p>这里看名字就可以猜出个所以然来：分别可以对应文件IO、UDP和TCP（Server和Client）。下面演示的案例基本上就是围绕这4个类型的Channel进行陈述的。</p>
<h3 id="Buffer"><a href="#Buffer" class="headerlink" title="Buffer"></a>Buffer</h3><p>NIO中的关键Buffer实现有：ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer，分别对应基本数据类型: byte, char, double, float, int, long, short。当然NIO中还有MappedByteBuffer, HeapByteBuffer, DirectByteBuffer等这里先不进行陈述。</p>
<h3 id="Selector"><a href="#Selector" class="headerlink" title="Selector"></a>Selector</h3><p>Selector运行单线程处理多个Channel，如果你的应用打开了多个通道，但每个连接的流量都很低，使用Selector就会很方便。例如在一个聊天服务器中。要使用Selector, 得向Selector注册Channel，然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回，线程就可以处理这些事件，事件的例子有如新的连接进来、数据接收等。</p>
<h2 id="二、FileChannel"><a href="#二、FileChannel" class="headerlink" title="二、FileChannel"></a>二、FileChannel</h2><p>看完上面的陈述，对于第一次接触NIO的同学来说云里雾里，只说了一些概念，也没记住什么，更别说怎么用了。这里开始通过传统IO以及更改后的NIO来做对比，以更形象的突出NIO的用法，进而使你对NIO有一点点的了解。</p>
<h3 id="传统IO-vs-NIO"><a href="#传统IO-vs-NIO" class="headerlink" title="传统IO vs NIO"></a>传统IO vs NIO</h3><p>首先，案例1是采用FileInputStream读取文件内容的：</p>
<p>   public static void method2(){</p>
<pre><code>InputStream in = null;

try{

    in = new BufferedInputStream(new FileInputStream(&quot;src/nomal_io.txt&quot;));

    byte [] buf = new byte[1024];

    int bytesRead = in.read(buf);

    while(bytesRead != -1)

    {

        for(int i=0;i&lt;bytesRead;i++)

            System.out.print((char)buf[i]);

        bytesRead = in.read(buf);

    }

}catch (IOException e)

{

    e.printStackTrace();

}finally{

    try{

        if(in != null){

            in.close();

        }

    }catch (IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>输出结果：（略）</p>
<p>案例是对应的NIO（这里通过RandomAccessFile进行操作，当然也可以通过FileInputStream.getChannel()进行操作）：</p>
<p>   public static void method1(){</p>
<pre><code>RandomAccessFile aFile = null;

try{

    aFile = new RandomAccessFile(&quot;src/nio.txt&quot;,&quot;rw&quot;);

    FileChannel fileChannel = aFile.getChannel();

    ByteBuffer buf = ByteBuffer.allocate(1024);

    int bytesRead = fileChannel.read(buf);

    System.out.println(bytesRead);

    while(bytesRead != -1)

    {

        buf.flip();

        while(buf.hasRemaining())

        {

            System.out.print((char)buf.get());

        }

        buf.compact();

        bytesRead = fileChannel.read(buf);

    }

}catch (IOException e){

    e.printStackTrace();

}finally{

    try{

        if(aFile != null){

            aFile.close();

        }

    }catch (IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>输出结果：（略）</p>
<p>通过仔细对比案例1和案例2，应该能看出个大概，最起码能发现NIO的实现方式比叫复杂。有了一个大概的印象可以进入下一步了。</p>
<h3 id="Buffer的使用"><a href="#Buffer的使用" class="headerlink" title="Buffer的使用"></a>Buffer的使用</h3><p>从案例2中可以总结出使用Buffer一般遵循下面几个步骤：</p>
<ul>
<li><p>分配空间（ByteBuffer buf = ByteBuffer.allocate(1024); 还有一种allocateDirector后面再陈述）</p>
</li>
<li><p>写入数据到Buffer(int bytesRead = fileChannel.read(buf);)</p>
</li>
<li><p>调用filp()方法（ buf.flip();）</p>
</li>
<li><p>从Buffer中读取数据（System.out.print((char)buf.get());）</p>
</li>
<li><p>调用clear()方法或者compact()方法</p>
</li>
</ul>
<p>Buffer顾名思义：缓冲区，实际上是一个容器，一个连续数组。Channel提供从文件、网络读取数据的渠道，但是读写的数据都必须经过Buffer。如下图：</p>
<p>向Buffer中写数据：</p>
<ul>
<li><p>从Channel写到Buffer (fileChannel.read(buf))</p>
</li>
<li><p>通过Buffer的put()方法 （buf.put(…)）</p>
</li>
</ul>
<p>从Buffer中读取数据：</p>
<ul>
<li><p>从Buffer读取到Channel (channel.write(buf))</p>
</li>
<li><p>使用get()方法从Buffer中读取数据 （buf.get()）</p>
</li>
</ul>
<p>可以把Buffer简单地理解为一组基本数据类型的元素列表，它通过几个变量来保存这个数据的当前位置状态：capacity, position, limit, mark：</p>
<p> <strong>索引</strong></p>
<p>| </p>
<p> <strong>说明</strong>  </p>
<p>—|—  </p>
<p>capacity</p>
<p>| </p>
<p>缓冲区数组的总长度  </p>
<p>position</p>
<p>| </p>
<p>下一个要操作的数据元素的位置  </p>
<p>limit</p>
<p>| </p>
<p>缓冲区数组中不可操作的下一个元素的位置：limit&lt;=capacity  </p>
<p>mark</p>
<p>| </p>
<p>用于记录当前position的前一个位置或者默认是-1  </p>
<p>无图无真相，举例：我们通过ByteBuffer.allocate(11)方法创建了一个11个byte的数组的缓冲区，初始状态如上图，position的位置为0，capacity和limit默认都是数组长度。当我们写入5个字节时，变化如下图：</p>
<p>这时我们需要将缓冲区中的5个字节数据写入Channel的通信信道，所以我们调用ByteBuffer.flip()方法，变化如下图所示(position设回0，并将limit设成之前的position的值)：</p>
<p>这时底层操作系统就可以从缓冲区中正确读取这个5个字节数据并发送出去了。在下一次写数据之前我们再调用clear()方法，缓冲区的索引位置又回到了初始位置。</p>
<p>调用clear()方法：position将被设回0，limit设置成capacity，换句话说，Buffer被清空了，其实Buffer中的数据并未被清除，只是这些标记告诉我们可以从哪里开始往Buffer里写数据。如果Buffer中有一些未读的数据，调用clear()方法，数据将“被遗忘”，意味着不再有任何标记会告诉你哪些数据被读过，哪些还没有。如果Buffer中仍有未读的数据，且后续还需要这些数据，但是此时想要先写些数据，那么使用compact()方法。compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样，设置成capacity。现在Buffer准备好写数据了，但是不会覆盖未读的数据。</p>
<p>通过调用Buffer.mark()方法，可以标记Buffer中的一个特定的position，之后可以通过调用Buffer.reset()方法恢复到这个position。Buffer.rewind()方法将position设回0，所以你可以重读Buffer中的所有数据。limit保持不变，仍然表示能从Buffer中读取多少个元素。</p>
<h2 id="三、SocketChannel"><a href="#三、SocketChannel" class="headerlink" title="三、SocketChannel"></a>三、SocketChannel</h2><p>说完了FileChannel和Buffer, 大家应该对Buffer的用法比较了解了，这里使用SocketChannel来继续探讨NIO。NIO的强大功能部分来自于Channel的非阻塞特性，套接字的某些操作可能会无限期地阻塞。例如，对accept()方法的调用可能会因为等待一个客户端连接而阻塞；对read()方法的调用可能会因为没有数据可读而阻塞，直到连接的另一端传来新的数据。总的来说，创建/接收连接或读写数据等I/O调用，都可能无限期地阻塞等待，直到底层的网络实现发生了什么。慢速的，有损耗的网络，或仅仅是简单的网络故障都可能导致任意时间的延迟。然而不幸的是，在调用一个方法之前无法知道其是否阻塞。NIO的channel抽象的一个重要特征就是可以通过配置它的阻塞行为，以实现非阻塞式的信道。</p>
<pre><code>channel.configureBlocking(false)</code></pre><p>在非阻塞式信道上调用一个方法总是会立即返回。这种调用的返回值指示了所请求的操作完成的程度。例如，在一个非阻塞式ServerSocketChannel上调用accept()方法，如果有连接请求来了，则返回客户端SocketChannel，否则返回null。</p>
<p>这里先举一个TCP应用案例，客户端采用NIO实现，而服务端依旧使用BIO实现。</p>
<p>客户端代码（案例3）：</p>
<p>   public static void client(){</p>
<pre><code>ByteBuffer buffer = ByteBuffer.allocate(1024);

SocketChannel socketChannel = null;

try

{

    socketChannel = SocketChannel.open();

    socketChannel.configureBlocking(false);

    socketChannel.connect(new InetSocketAddress(&quot;10.10.195.115&quot;,8080));

    if(socketChannel.finishConnect())

    {

        int i=0;

        while(true)

        {

            TimeUnit.SECONDS.sleep(1);

            String info = &quot;I&apos;m &quot;+i+++&quot;-th information from client&quot;;

            buffer.clear();

            buffer.put(info.getBytes());

            buffer.flip();

            while(buffer.hasRemaining()){

                System.out.println(buffer);

                socketChannel.write(buffer);

            }

        }

    }

}

catch (IOException | InterruptedException e)

{

    e.printStackTrace();

}

finally{

    try{

        if(socketChannel!=null){

            socketChannel.close();

        }

    }catch(IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>服务端代码（案例4）：</p>
<p>   public static void server(){</p>
<pre><code>ServerSocket serverSocket = null;

InputStream in = null;

try

{

    serverSocket = new ServerSocket(8080);

    int recvMsgSize = 0;

    byte[] recvBuf = new byte[1024];

    while(true){

        Socket clntSocket = serverSocket.accept();

        SocketAddress clientAddress = clntSocket.getRemoteSocketAddress();

        System.out.println(&quot;Handling client at &quot;+clientAddress);

        in = clntSocket.getInputStream();

        while((recvMsgSize=in.read(recvBuf))!=-1){

            byte[] temp = new byte[recvMsgSize];

            System.arraycopy(recvBuf, 0, temp, 0, recvMsgSize);

            System.out.println(new String(temp));

        }

    }

}

catch (IOException e)

{

    e.printStackTrace();

}

finally{

    try{

        if(serverSocket!=null){

            serverSocket.close();

        }

        if(in!=null){

            in.close();

        }

    }catch(IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>输出结果：（略）</p>
<p>根据案例分析，总结一下SocketChannel的用法。</p>
<p>打开SocketChannel：</p>
<pre><code>socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress(&quot;10.10.195.115&quot;,8080));</code></pre><p>关闭：</p>
<pre><code>socketChannel.close();</code></pre><p>读取数据：</p>
<pre><code>String info = &quot;I&apos;m &quot;+i+++&quot;-th information from client&quot;;

buffer.clear();

buffer.put(info.getBytes());

buffer.flip();

while(buffer.hasRemaining()){

    System.out.println(buffer);

    socketChannel.write(buffer);

}</code></pre><p>注意SocketChannel.write()方法的调用是在一个while循环中的。write()方法无法保证能写多少字节到SocketChannel。所以，我们重复调用write()直到Buffer没有要写的字节为止。</p>
<p>非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值，它会告诉你读取了多少字节。</p>
<h2 id="四、TCP服务端的NIO写法"><a href="#四、TCP服务端的NIO写法" class="headerlink" title="四、TCP服务端的NIO写法"></a>四、TCP服务端的NIO写法</h2><p>到目前为止，所举的案例中都没有涉及Selector。不要急，好东西要慢慢来。Selector类可以用于避免使用阻塞式客户端中很浪费资源的“忙等”方法。例如，考虑一个IM服务器。像QQ或者旺旺这样的，可能有几万甚至几千万个客户端同时连接到了服务器，但在任何时刻都只是非常少量的消息。</p>
<p>需要读取和分发。这就需要一种方法阻塞等待，直到至少有一个信道可以进行I/O操作，并指出是哪个信道。NIO的选择器就实现了这样的功能。一个Selector实例可以同时检查一组信道的I/O状态。用专业术语来说，选择器就是一个多路开关选择器，因为一个选择器能够管理多个信道上的I/O操作。然而如果用传统的方式来处理这么多客户端，使用的方法是循环地一个一个地去检查所有的客户端是否有I/O操作，如果当前客户端有I/O操作，则可能把当前客户端扔给一个线程池去处理，如果没有I/O操作则进行下一个轮询，当所有的客户端都轮询过了又接着从头开始轮询；这种方法是非常笨而且也非常浪费资源，因为大部分客户端是没有I/O操作，我们也要去检查；而Selector就不一样了，它在内部可以同时管理多个I/O，当一个信道有I/O操作的时候，他会通知Selector，Selector就是记住这个信道有I/O操作，并且知道是何种I/O操作，是读呢？是写呢？还是接受新的连接；所以如果使用Selector，它返回的结果只有两种结果，一种是0，即在你调用的时刻没有任何客户端需要I/O操作，另一种结果是一组需要I/O操作的客户端，这时你就根本不需要再检查了，因为它返回给你的肯定是你想要的。这样一种通知的方式比那种主动轮询的方式要高效得多！</p>
<p>要使用选择器（Selector），需要创建一个Selector实例（使用静态工厂方法open()）并将其注册（register）到想要监控的信道上（注意，这要通过channel的方法实现，而不是使用selector的方法）。最后，调用选择器的select()方法。该方法会阻塞等待，直到有一个或更多的信道准备好了I/O操作或等待超时。select()方法将返回可进行I/O操作的信道数量。现在，在一个单独的线程中，通过调用select()方法就能检查多个信道是否准备好进行I/O操作。如果经过一段时间后仍然没有信道准备好，select()方法就会返回0，并允许程序继续执行其他任务。</p>
<p>下面将上面的TCP服务端代码改写成NIO的方式（案例5）：</p>
<p>public class ServerConnect</p>
<p>{</p>
<p>   private static final int BUF_SIZE=1024;</p>
<p>   private static final int PORT = 8080;</p>
<p>   private static final int TIMEOUT = 3000;</p>
<p>   public static void main(String[] args)</p>
<p>   {</p>
<pre><code>selector();</code></pre><p>   }</p>
<p>   public static void handleAccept(SelectionKey key) throws IOException{</p>
<pre><code>ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();

SocketChannel sc = ssChannel.accept();

sc.configureBlocking(false);

sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUF_SIZE));</code></pre><p>   }</p>
<p>   public static void handleRead(SelectionKey key) throws IOException{</p>
<pre><code>SocketChannel sc = (SocketChannel)key.channel();

ByteBuffer buf = (ByteBuffer)key.attachment();

long bytesRead = sc.read(buf);

while(bytesRead&gt;0){

    buf.flip();

    while(buf.hasRemaining()){

        System.out.print((char)buf.get());

    }

    System.out.println();

    buf.clear();

    bytesRead = sc.read(buf);

}

if(bytesRead == -1){

    sc.close();

}</code></pre><p>   }</p>
<p>   public static void handleWrite(SelectionKey key) throws IOException{</p>
<pre><code>ByteBuffer buf = (ByteBuffer)key.attachment();

buf.flip();

SocketChannel sc = (SocketChannel) key.channel();

while(buf.hasRemaining()){

    sc.write(buf);

}

buf.compact();</code></pre><p>   }</p>
<p>   public static void selector() {</p>
<pre><code>Selector selector = null;

ServerSocketChannel ssc = null;

try{

    selector = Selector.open();

    ssc= ServerSocketChannel.open();

    ssc.socket().bind(new InetSocketAddress(PORT));

    ssc.configureBlocking(false);

    ssc.register(selector, SelectionKey.OP_ACCEPT);

    while(true){

        if(selector.select(TIMEOUT) == 0){

            System.out.println(&quot;==&quot;);

            continue;

        }

        Iterator&lt;SelectionKey&gt; iter = selector.selectedKeys().iterator();

        while(iter.hasNext()){

            SelectionKey key = iter.next();

            if(key.isAcceptable()){

                handleAccept(key);

            }

            if(key.isReadable()){

                handleRead(key);

            }

            if(key.isWritable() &amp;&amp; key.isValid()){

                handleWrite(key);

            }

            if(key.isConnectable()){

                System.out.println(&quot;isConnectable = true&quot;);

            }

            iter.remove();

        }

    }

}catch(IOException e){

    e.printStackTrace();

}finally{

    try{

        if(selector!=null){

            selector.close();

        }

        if(ssc!=null){

            ssc.close();

        }

    }catch(IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>}</p>
<p>下面来慢慢讲解这段代码。</p>
<h3 id="ServerSocketChannel"><a href="#ServerSocketChannel" class="headerlink" title="ServerSocketChannel"></a>ServerSocketChannel</h3><p>打开ServerSocketChannel：</p>
<p>ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();</p>
<p>关闭ServerSocketChannel：</p>
<p>serverSocketChannel.close();</p>
<p>监听新进来的连接：</p>
<p>while(true){</p>
<p>   SocketChannel socketChannel = serverSocketChannel.accept();</p>
<p>}</p>
<p>ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下，accept() 方法会立刻返回，如果还没有新进来的连接,返回的将是null。 因此，需要检查返回的SocketChannel是否是null.如：</p>
<pre><code>ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));

serverSocketChannel.configureBlocking(false);

while (true)

{

    SocketChannel socketChannel = serverSocketChannel.accept();

    if (socketChannel != null)

    {

        // do something with socketChannel...

    }

}</code></pre><h3 id="Selector-1"><a href="#Selector-1" class="headerlink" title="Selector"></a>Selector</h3><p>Selector的创建：Selector selector = Selector.open();</p>
<p>为了将Channel和Selector配合使用，必须将Channel注册到Selector上，通过SelectableChannel.register()方法来实现，沿用案例5中的部分代码：</p>
<pre><code>ssc= ServerSocketChannel.open();

ssc.socket().bind(new InetSocketAddress(PORT));

ssc.configureBlocking(false);

ssc.register(selector, SelectionKey.OP_ACCEPT);</code></pre><p>与Selector一起使用时，Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用，因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。</p>
<p>注意register()方法的第二个参数。这是一个“interest集合”，意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件：</p>
<ol>
<li><p>Connect</p>
</li>
<li><p>Accept</p>
</li>
<li><p>Read</p>
</li>
<li><p>Write</p>
</li>
</ol>
<p>通道触发了一个事件意思是该事件已经就绪。所以，某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。</p>
<p>这四种事件用SelectionKey的四个常量来表示：</p>
<ol>
<li><p>SelectionKey.OP_CONNECT</p>
</li>
<li><p>SelectionKey.OP_ACCEPT</p>
</li>
<li><p>SelectionKey.OP_READ</p>
</li>
<li><p>SelectionKey.OP_WRITE</p>
</li>
</ol>
<h3 id="SelectionKey"><a href="#SelectionKey" class="headerlink" title="SelectionKey"></a>SelectionKey</h3><p>当向Selector注册Channel时，register()方法会返回一个SelectionKey对象。这个对象包含了一些你感兴趣的属性：</p>
<ul>
<li><p>interest集合</p>
</li>
<li><p>ready集合</p>
</li>
<li><p>Channel</p>
</li>
<li><p>Selector</p>
</li>
<li><p>附加的对象（可选）</p>
</li>
</ul>
<p>interest集合：就像向Selector注册通道一节中所描述的，interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合。</p>
<p>ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后，你会首先访问这个ready set。Selection将在下一小节进行解释。可以这样访问ready集合：</p>
<p>int readySet = selectionKey.readyOps();</p>
<p>可以用像检测interest集合那样的方法，来检测channel中什么事件或操作已经就绪。但是，也可以使用以下四个方法，它们都会返回一个布尔类型：</p>
<p>selectionKey.isAcceptable();</p>
<p>selectionKey.isConnectable();</p>
<p>selectionKey.isReadable();</p>
<p>selectionKey.isWritable();</p>
<p>从SelectionKey访问Channel和Selector很简单。如下：</p>
<p>Channel  channel  = selectionKey.channel();</p>
<p>Selector selector = selectionKey.selector();</p>
<p>可以将一个对象或者更多信息附着到SelectionKey上，这样就能方便的识别某个给定的通道。例如，可以附加 与通道一起使用的Buffer，或是包含聚集数据的某个对象。使用方法如下：</p>
<p>selectionKey.attach(theObject);</p>
<p>Object attachedObj = selectionKey.attachment();</p>
<p>还可以在用register()方法向Selector注册Channel的时候附加对象。如：</p>
<p>SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);</p>
<h3 id="通过Selector选择通道"><a href="#通过Selector选择通道" class="headerlink" title="通过Selector选择通道"></a>通过Selector选择通道</h3><p>一旦向Selector注册了一或多个通道，就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件（如连接、接受、读或写）已经准备就绪的那些通道。换句话说，如果你对“读就绪”的通道感兴趣，select()方法会返回读事件已经就绪的那些通道。</p>
<p>下面是select()方法：</p>
<ul>
<li><p>int select()</p>
</li>
<li><p>int select(long timeout)</p>
</li>
<li><p>int selectNow()</p>
</li>
</ul>
<p>select()阻塞到至少有一个通道在你注册的事件上就绪了。</p>
<p>select(long timeout)和select()一样，除了最长会阻塞timeout毫秒(参数)。</p>
<p>selectNow()不会阻塞，不管什么通道就绪都立刻返回（译者注：此方法执行非阻塞的选择操作。如果自从前一次选择操作后，没有通道变成可选择的，则此方法直接返回零。）。</p>
<p>select()方法返回的int值表示有多少通道已经就绪。亦即，自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法，因为有一个通道变成就绪状态，返回了1，若再次调用select()方法，如果另一个通道就绪了，它会再次返回1。如果对第一个就绪的channel没有做任何操作，现在就有两个就绪的通道，但在每次select()方法调用之间，只有一个通道就绪了。</p>
<p>一旦调用了select()方法，并且返回值表明有一个或更多个通道就绪了，然后可以通过调用selector的selectedKeys()方法，访问“已选择键集（selected key set）”中的就绪通道。如下所示：</p>
<p>Set selectedKeys = selector.selectedKeys();</p>
<p>当向Selector注册Channel时，Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。</p>
<p>注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时，Selector会再次将其放入已选择键集中。</p>
<p>SelectionKey.channel()方法返回的通道需要转型成你要处理的类型，如ServerSocketChannel或SocketChannel等。</p>
<p>一个完整的使用Selector和ServerSocketChannel的案例可以参考案例5的selector()方法。</p>
<hr>
<h2 id="五、内存映射文件"><a href="#五、内存映射文件" class="headerlink" title="五、内存映射文件"></a>五、内存映射文件</h2><p>JAVA处理大文件，一般用BufferedReader,BufferedInputStream这类带缓冲的IO类，不过如果文件超大的话，更快的方式是采用MappedByteBuffer。</p>
<p>MappedByteBuffer是NIO引入的文件内存映射方案，读写性能极高。NIO最主要的就是实现了对异步操作的支持。其中一种通过把一个套接字通道(SocketChannel)注册到一个选择器(Selector)中,不时调用后者的选择(select)方法就能返回满足的选择键(SelectionKey),键中包含了SOCKET事件信息。这就是select模型。</p>
<p>SocketChannel的读写是通过一个类叫ByteBuffer来操作的.这个类本身的设计是不错的,比直接操作byte[]方便多了. ByteBuffer有两种模式:直接/间接.间接模式最典型(也只有这么一种)的就是HeapByteBuffer,即操作堆内存 (byte[]).但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存.这时就必须使用”直接”模式,即 MappedByteBuffer,文件映射.</p>
<p>先中断一下,谈谈操作系统的内存管理.一般操作系统的内存分两部分:物理内存;虚拟内存.虚拟内存一般使用的是页面映像文件,即硬盘中的某个(某些)特殊的文件.操作系统负责页面文件内容的读写,这个过程叫”页面中断/切换”. MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个ByteBuffer.MappedByteBuffer 只是一种特殊的ByteBuffer，即是ByteBuffer的子类。 MappedByteBuffer 将文件直接映射到内存（这里的内存指的是虚拟内存，并不是物理内存）。通常，可以映射整个文件，如果文件比较大的话可以分段进行映射，只要指定文件的那个部分就可以。</p>
<h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>FileChannel提供了map方法来把文件影射为内存映像文件： MappedByteBuffer map(int mode,long position,long size); 可以把文件的从position开始的size大小的区域映射为内存映像文件，mode指出了 可访问该内存映像文件的方式：</p>
<ul>
<li><p>READ_ONLY,（只读）： 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)</p>
</li>
<li><p>READ_WRITE（读/写）： 对得到的缓冲区的更改最终将传播到文件；该更改对映射到同一文件的其他程序不一定是可见的。 (MapMode.READ_WRITE)</p>
</li>
<li><p>PRIVATE（专用）： 对得到的缓冲区的更改不会传播到文件，并且该更改对映射到同一文件的其他程序也不是可见的；相反，会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE)</p>
</li>
</ul>
<p>MappedByteBuffer是ByteBuffer的子类，其扩充了三个方法：</p>
<ul>
<li><p>force()：缓冲区是READ_WRITE模式下，此方法对缓冲区内容的修改强行写入文件；</p>
</li>
<li><p>load()：将缓冲区的内容载入内存，并返回该缓冲区的引用；</p>
</li>
<li><p>isLoaded()：如果缓冲区的内容在物理内存中，则返回真，否则返回假；</p>
</li>
</ul>
<h3 id="案例对比"><a href="#案例对比" class="headerlink" title="案例对比"></a>案例对比</h3><p>这里通过采用ByteBuffer和MappedByteBuffer分别读取大小约为5M的文件”src/1.ppt”来比较两者之间的区别，method3()是采用MappedByteBuffer读取的，method4()对应的是ByteBuffer。</p>
<p>   public static void method4(){</p>
<pre><code>RandomAccessFile aFile = null;

FileChannel fc = null;

try{

    aFile = new RandomAccessFile(&quot;src/1.ppt&quot;,&quot;rw&quot;);

    fc = aFile.getChannel();

    long timeBegin = System.currentTimeMillis();

    ByteBuffer buff = ByteBuffer.allocate((int) aFile.length());

    buff.clear();

    fc.read(buff);

    //System.out.println((char)buff.get((int)(aFile.length()/2-1)));

    //System.out.println((char)buff.get((int)(aFile.length()/2)));

    //System.out.println((char)buff.get((int)(aFile.length()/2)+1));

    long timeEnd = System.currentTimeMillis();

    System.out.println(&quot;Read time: &quot;+(timeEnd-timeBegin)+&quot;ms&quot;);

}catch(IOException e){

    e.printStackTrace();

}finally{

    try{

        if(aFile!=null){

            aFile.close();

        }

        if(fc!=null){

            fc.close();

        }

    }catch(IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>   public static void method3(){</p>
<pre><code>RandomAccessFile aFile = null;

FileChannel fc = null;

try{

    aFile = new RandomAccessFile(&quot;src/1.ppt&quot;,&quot;rw&quot;);

    fc = aFile.getChannel();

    long timeBegin = System.currentTimeMillis();

    MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, aFile.length());

    // System.out.println((char)mbb.get((int)(aFile.length()/2-1)));

    // System.out.println((char)mbb.get((int)(aFile.length()/2)));

    //System.out.println((char)mbb.get((int)(aFile.length()/2)+1));

    long timeEnd = System.currentTimeMillis();

    System.out.println(&quot;Read time: &quot;+(timeEnd-timeBegin)+&quot;ms&quot;);

}catch(IOException e){

    e.printStackTrace();

}finally{

    try{

        if(aFile!=null){

            aFile.close();

        }

        if(fc!=null){

            fc.close();

        }

    }catch(IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>通过在入口函数main()中运行：</p>
<pre><code>method3();

System.out.println(&quot;=============&quot;);

method4();</code></pre><p>输出结果（运行在普通PC机上）：</p>
<p>Read time: 2ms</p>
<p>=============</p>
<p>Read time: 12ms</p>
<p>通过输出结果可以看出彼此的差别，一个例子也许是偶然，那么下面把5M大小的文件替换为200M的文件，输出结果：</p>
<p>Read time: 1ms</p>
<p>=============</p>
<p>Read time: 407ms</p>
<p>可以看到差距拉大。</p>
<blockquote>
<p>注：MappedByteBuffer有资源释放的问题：被MappedByteBuffer打开的文件只有在垃圾收集时才会被关闭，而这个点是不确定的。在Javadoc中这里描述：A mapped byte buffer and the file mapping that it represents remian valid until the buffer itself is garbage-collected。详细可以翻阅参考资料5和6.</p>
</blockquote>
<hr>
<h2 id="六、其余功能介绍"><a href="#六、其余功能介绍" class="headerlink" title="六、其余功能介绍"></a>六、其余功能介绍</h2><p>看完以上陈述，详细大家对NIO有了一定的了解，下面主要通过几个案例，来说明NIO的其余功能，下面代码量偏多，功能性讲述偏少。</p>
<h3 id="Scatter-Gatter"><a href="#Scatter-Gatter" class="headerlink" title="Scatter/Gatter"></a>Scatter/Gatter</h3><p>分散（scatter）从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此，Channel将从Channel中读取的数据“分散（scatter）”到多个Buffer中。</p>
<p>聚集（gather）写入Channel是指在写操作时将多个buffer的数据写入同一个Channel，因此，Channel 将多个Buffer中的数据“聚集（gather）”后发送到Channel。</p>
<p>scatter / gather经常用于需要将传输的数据分开处理的场合，例如传输一个由消息头和消息体组成的消息，你可能会将消息体和消息头分散到不同的buffer中，这样你可以方便的处理消息头和消息体。</p>
<p>案例：</p>
<p>import java.io.File;</p>
<p>import java.io.FileNotFoundException;</p>
<p>import java.io.FileOutputStream;</p>
<p>import java.io.IOException;</p>
<p>import java.io.OutputStream;</p>
<p>import java.nio.ByteBuffer;</p>
<p>import java.nio.channels.Channel;</p>
<p>import java.nio.channels.FileChannel;</p>
<p>public class ScattingAndGather</p>
<p>{</p>
<p>   public static void main(String args[]){</p>
<pre><code>gather();</code></pre><p>   }</p>
<p>   public static void gather()</p>
<p>   {</p>
<pre><code>ByteBuffer header = ByteBuffer.allocate(10);

ByteBuffer body = ByteBuffer.allocate(10);

byte [] b1 = {&apos;0&apos;, &apos;1&apos;};

byte [] b2 = {&apos;2&apos;, &apos;3&apos;};

header.put(b1);

body.put(b2);

ByteBuffer [] buffs = {header, body};

try

{

    FileOutputStream os = new FileOutputStream(&quot;src/scattingAndGather.txt&quot;);

    FileChannel channel = os.getChannel();

    channel.write(buffs);

}

catch (IOException e)

{

    e.printStackTrace();

}</code></pre><p>   }</p>
<p>}</p>
<h3 id="transferFrom-amp-transferTo"><a href="#transferFrom-amp-transferTo" class="headerlink" title="transferFrom &amp; transferTo"></a>transferFrom &amp; transferTo</h3><p>FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中。</p>
<p>   public static void method1(){</p>
<pre><code>RandomAccessFile fromFile = null;

RandomAccessFile toFile = null;

try

{

    fromFile = new RandomAccessFile(&quot;src/fromFile.xml&quot;,&quot;rw&quot;);

    FileChannel fromChannel = fromFile.getChannel();

    toFile = new RandomAccessFile(&quot;src/toFile.txt&quot;,&quot;rw&quot;);

    FileChannel toChannel = toFile.getChannel();

    long position = 0;

    long count = fromChannel.size();

    System.out.println(count);

    toChannel.transferFrom(fromChannel, position, count);

}

catch (IOException e)

{

    e.printStackTrace();

}

finally{

    try{

        if(fromFile != null){

            fromFile.close();

        }

        if(toFile != null){

            toFile.close();

        }

    }

    catch(IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>方法的输入参数position表示从position处开始向目标文件写入数据，count表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节，则所传输的字节数要小于请求的字节数。此外要注意，在SoketChannel的实现中，SocketChannel只会传输此刻准备好的数据（可能不足count字节）。因此，SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中。</p>
<p>transferTo()方法将数据从FileChannel传输到其他的channel中。</p>
<p>   public static void method2()</p>
<p>   {</p>
<pre><code>RandomAccessFile fromFile = null;

RandomAccessFile toFile = null;

try

{

    fromFile = new RandomAccessFile(&quot;src/fromFile.txt&quot;,&quot;rw&quot;);

    FileChannel fromChannel = fromFile.getChannel();

    toFile = new RandomAccessFile(&quot;src/toFile.txt&quot;,&quot;rw&quot;);

    FileChannel toChannel = toFile.getChannel();

    long position = 0;

    long count = fromChannel.size();

    System.out.println(count);

    fromChannel.transferTo(position, count,toChannel);

}

catch (IOException e)

{

    e.printStackTrace();

}

finally{

    try{

        if(fromFile != null){

            fromFile.close();

        }

        if(toFile != null){

            toFile.close();

        }

    }

    catch(IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>上面所说的关于SocketChannel的问题在transferTo()方法中同样存在。SocketChannel会一直传输数据直到目标buffer被填满。</p>
<h3 id="Pipe"><a href="#Pipe" class="headerlink" title="Pipe"></a>Pipe</h3><p>Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道，从source通道读取。</p>
<p>   public static void method1(){</p>
<pre><code>Pipe pipe = null;

ExecutorService exec = Executors.newFixedThreadPool(2);

try{

    pipe = Pipe.open();

    final Pipe pipeTemp = pipe;

    exec.submit(new Callable&lt;Object&gt;(){

        @Override

        public Object call() throws Exception

        {

            Pipe.SinkChannel sinkChannel = pipeTemp.sink();//向通道中写数据

            while(true){

                TimeUnit.SECONDS.sleep(1);

                String newData = &quot;Pipe Test At Time &quot;+System.currentTimeMillis();

                ByteBuffer buf = ByteBuffer.allocate(1024);

                buf.clear();

                buf.put(newData.getBytes());

                buf.flip();

                while(buf.hasRemaining()){

                    System.out.println(buf);

                    sinkChannel.write(buf);

                }

            }

        }

    });

    exec.submit(new Callable&lt;Object&gt;(){

        @Override

        public Object call() throws Exception

        {

            Pipe.SourceChannel sourceChannel = pipeTemp.source();//向通道中读数据

            while(true){

                TimeUnit.SECONDS.sleep(1);

                ByteBuffer buf = ByteBuffer.allocate(1024);

                buf.clear();

                int bytesRead = sourceChannel.read(buf);

                System.out.println(&quot;bytesRead=&quot;+bytesRead);

                while(bytesRead &gt;0 ){

                    buf.flip();

                    byte b[] = new byte[bytesRead];

                    int i=0;

                    while(buf.hasRemaining()){

                        b[i]=buf.get();

                        System.out.printf(&quot;%X&quot;,b[i]);

                        i++;

                    }

                    String s = new String(b);

                    System.out.println(&quot;=================||&quot;+s);

                    bytesRead = sourceChannel.read(buf);

                }

            }

        }

    });

}catch(IOException e){

    e.printStackTrace();

}finally{

    exec.shutdown();

}</code></pre><p>   }</p>
<h3 id="DatagramChannel"><a href="#DatagramChannel" class="headerlink" title="DatagramChannel"></a>DatagramChannel</h3><p>Java NIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议，所以不能像其它通道那样读取和写入。它发送和接收的是数据包。</p>
<p>   public static void  reveive(){</p>
<pre><code>DatagramChannel channel = null;

try{

    channel = DatagramChannel.open();

    channel.socket().bind(new InetSocketAddress(8888));

    ByteBuffer buf = ByteBuffer.allocate(1024);

    buf.clear();

    channel.receive(buf);

    buf.flip();

    while(buf.hasRemaining()){

        System.out.print((char)buf.get());

    }

    System.out.println();

}catch(IOException e){

    e.printStackTrace();

}finally{

    try{

        if(channel!=null){

            channel.close();

        }

    }catch(IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>   public static void send(){</p>
<pre><code>DatagramChannel channel = null;

try{

    channel = DatagramChannel.open();

    String info = &quot;I&apos;m the Sender!&quot;;

    ByteBuffer buf = ByteBuffer.allocate(1024);

    buf.clear();

    buf.put(info.getBytes());

    buf.flip();

    int bytesSent = channel.send(buf, new InetSocketAddress(&quot;10.10.195.115&quot;,8888));

    System.out.println(bytesSent);

}catch(IOException e){

    e.printStackTrace();

}finally{

    try{

        if(channel!=null){

            channel.close();

        }

    }catch(IOException e){

        e.printStackTrace();

    }

}</code></pre><p>   }</p>
<p>END</p>
<p>往</p>
<p>期</p>
<p>精</p>
<p>彩</p>
<p>《<a href="http://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483913&idx=1&sn=2da53737b8e8908cf3efdae9621c9698&chksm=fb0be89dcc7c618b0d5a1ba8ac654295454cfc2fa81fbae5a6de49bf0a91a305ca707e9864fc&scene=21#wechat_redirect" target="_blank" rel="noopener"></a><a href="http://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483913&idx=1&sn=2da53737b8e8908cf3efdae9621c9698&chksm=fb0be89dcc7c618b0d5a1ba8ac654295454cfc2fa81fbae5a6de49bf0a91a305ca707e9864fc&scene=21#wechat_redirect" target="_blank" rel="noopener">什么是Zero-Copy?</a>》</p>
<p>《<a href="http://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483907&idx=1&sn=3d5e1384a36bd59f5fd14135067af1c2&chksm=fb0be897cc7c61815a6a1c3181f3ba3507b199fd7a8c9025e9d8f67b5e9783bc0f0fe1c73903&scene=21#wechat_redirect" target="_blank" rel="noopener"></a><a href="http://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483907&idx=1&sn=3d5e1384a36bd59f5fd14135067af1c2&chksm=fb0be897cc7c61815a6a1c3181f3ba3507b199fd7a8c9025e9d8f67b5e9783bc0f0fe1c73903&scene=21#wechat_redirect" target="_blank" rel="noopener">NIO理论基础篇</a>》</p>
<p>《<a href="http://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483900&idx=1&sn=ba80570485038c7db21c89706da3abab&chksm=fb0beb68cc7c627e6dd5dec841c1e62ef2e9ffb9fdd9a76e7511d6016b58395b86534b63a8bd&scene=21#wechat_redirect" target="_blank" rel="noopener"></a><a href="http://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483900&idx=1&sn=ba80570485038c7db21c89706da3abab&chksm=fb0beb68cc7c627e6dd5dec841c1e62ef2e9ffb9fdd9a76e7511d6016b58395b86534b63a8bd&scene=21#wechat_redirect" target="_blank" rel="noopener">消息中间件选型分析</a>》</p>
<p><a href="https://mp.weixin.qq.com/s/c9tkrokcDQR375kiwCeV9w?#" target="_blank" rel="noopener">阅读原文</a></p>

  </div>
</article>



        
          <div id="footer-post-container">
  <div id="footer-post">

    <div id="nav-footer" style="display: none">
      <ul>
         
          <li><a href="/">Home</a></li>
         
          <li><a href="/about/">About</a></li>
         
          <li><a href="/archives/">Writing</a></li>
         
          <li><a href="/projects_url">Projects</a></li>
        
      </ul>
    </div>

    <div id="toc-footer" style="display: none">
      <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-NIO？看这一篇就够了！"><span class="toc-number">1.</span> <span class="toc-text">Java NIO？看这一篇就够了！</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#一、概述"><span class="toc-number">2.</span> <span class="toc-text">一、概述</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Channel"><span class="toc-number">2.1.</span> <span class="toc-text">Channel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer"><span class="toc-number">2.2.</span> <span class="toc-text">Buffer</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector"><span class="toc-number">2.3.</span> <span class="toc-text">Selector</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#二、FileChannel"><span class="toc-number">3.</span> <span class="toc-text">二、FileChannel</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#传统IO-vs-NIO"><span class="toc-number">3.1.</span> <span class="toc-text">传统IO vs NIO</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer的使用"><span class="toc-number">3.2.</span> <span class="toc-text">Buffer的使用</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#三、SocketChannel"><span class="toc-number">4.</span> <span class="toc-text">三、SocketChannel</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#四、TCP服务端的NIO写法"><span class="toc-number">5.</span> <span class="toc-text">四、TCP服务端的NIO写法</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#ServerSocketChannel"><span class="toc-number">5.1.</span> <span class="toc-text">ServerSocketChannel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector-1"><span class="toc-number">5.2.</span> <span class="toc-text">Selector</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#SelectionKey"><span class="toc-number">5.3.</span> <span class="toc-text">SelectionKey</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#通过Selector选择通道"><span class="toc-number">5.4.</span> <span class="toc-text">通过Selector选择通道</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#五、内存映射文件"><span class="toc-number">6.</span> <span class="toc-text">五、内存映射文件</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#概念"><span class="toc-number">6.1.</span> <span class="toc-text">概念</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#案例对比"><span class="toc-number">6.2.</span> <span class="toc-text">案例对比</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#六、其余功能介绍"><span class="toc-number">7.</span> <span class="toc-text">六、其余功能介绍</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Scatter-Gatter"><span class="toc-number">7.1.</span> <span class="toc-text">Scatter&#x2F;Gatter</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#transferFrom-amp-transferTo"><span class="toc-number">7.2.</span> <span class="toc-text">transferFrom &amp; transferTo</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Pipe"><span class="toc-number">7.3.</span> <span class="toc-text">Pipe</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#DatagramChannel"><span class="toc-number">7.4.</span> <span class="toc-text">DatagramChannel</span></a></li></ol></li></ol>
    </div>

    <div id="share-footer" style="display: none">
      <ul>
  <li><a class="icon" href="http://www.facebook.com/sharer.php?u=http://yoursite.com/2019/05/28/JavaNIO/" target="_blank" rel="noopener"><i class="fab fa-facebook fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="https://twitter.com/share?url=http://yoursite.com/2019/05/28/JavaNIO/&text=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-twitter fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://www.linkedin.com/shareArticle?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-linkedin fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="https://pinterest.com/pin/create/bookmarklet/?url=http://yoursite.com/2019/05/28/JavaNIO/&is_video=false&description=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-pinterest fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="mailto:?subject=JavaNIO&body=Check out this article: http://yoursite.com/2019/05/28/JavaNIO/"><i class="fas fa-envelope fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="https://getpocket.com/save?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-get-pocket fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://reddit.com/submit?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-reddit fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://www.stumbleupon.com/submit?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-stumbleupon fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://digg.com/submit?url=http://yoursite.com/2019/05/28/JavaNIO/&title=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-digg fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="http://www.tumblr.com/share/link?url=http://yoursite.com/2019/05/28/JavaNIO/&name=JavaNIO&description=" target="_blank" rel="noopener"><i class="fab fa-tumblr fa-lg" aria-hidden="true"></i></a></li>
  <li><a class="icon" href="https://news.ycombinator.com/submitlink?u=http://yoursite.com/2019/05/28/JavaNIO/&t=JavaNIO" target="_blank" rel="noopener"><i class="fab fa-hacker-news fa-lg" aria-hidden="true"></i></a></li>
</ul>

    </div>

    <div id="actions-footer">
        <a id="menu" class="icon" href="#" onclick="$('#nav-footer').toggle();return false;"><i class="fas fa-bars fa-lg" aria-hidden="true"></i> Menu</a>
        <a id="toc" class="icon" href="#" onclick="$('#toc-footer').toggle();return false;"><i class="fas fa-list fa-lg" aria-hidden="true"></i> TOC</a>
        <a id="share" class="icon" href="#" onclick="$('#share-footer').toggle();return false;"><i class="fas fa-share-alt fa-lg" aria-hidden="true"></i> Share</a>
        <a id="top" style="display:none" class="icon" href="#" onclick="$('html, body').animate({ scrollTop: 0 }, 'fast');"><i class="fas fa-chevron-up fa-lg" aria-hidden="true"></i> Top</a>
    </div>

  </div>
</div>

        
        <footer id="footer">
  <div class="footer-left">
    Copyright &copy; 2020 Tmoonlight
  </div>
  <div class="footer-right">
    <nav>
      <ul>
         
          <li><a href="/">Home</a></li>
         
          <li><a href="/about/">About</a></li>
         
          <li><a href="/archives/">Writing</a></li>
         
          <li><a href="/projects_url">Projects</a></li>
        
      </ul>
    </nav>
  </div>
</footer>

    </div>
    <!-- styles -->

<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">


<link rel="stylesheet" href="/lib/justified-gallery/css/justifiedGallery.min.css">


    <!-- jquery -->

<script src="/lib/jquery/jquery.min.js"></script>


<script src="/lib/justified-gallery/js/jquery.justifiedGallery.min.js"></script>

<!-- clipboard -->

  
<script src="/lib/clipboard/clipboard.min.js"></script>

  <script type="text/javascript">
  $(function() {
    // copy-btn HTML
    var btn = "<span class=\"btn-copy tooltipped tooltipped-sw\" aria-label=\"Copy to clipboard!\">";
    btn += '<i class="far fa-clone"></i>';
    btn += '</span>'; 
    // mount it!
    $(".highlight table").before(btn);
    var clip = new ClipboardJS('.btn-copy', {
      text: function(trigger) {
        return Array.from(trigger.nextElementSibling.querySelectorAll('.code')).reduce((str,it)=>str+it.innerText+'\n','')
      }
    });
    clip.on('success', function(e) {
      e.trigger.setAttribute('aria-label', "Copied!");
      e.clearSelection();
    })
  })
  </script>


<script src="/js/main.js"></script>

<!-- search -->

<!-- Google Analytics -->

<!-- Baidu Analytics -->

<!-- Disqus Comments -->


</body>
</html>
